package com.atguigu.tingshu.user.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.model.user.UserListenProcess;
import com.atguigu.tingshu.model.user.UserPaidAlbum;
import com.atguigu.tingshu.model.user.UserPaidTrack;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.atguigu.tingshu.vo.user.UserListenProcessVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

	@Autowired
	private UserInfoMapper userInfoMapper;
    @Autowired
	private WxMaService wxMaService;
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private RabbitService rabbitService;
	@Autowired
	private UserPaidAlbumMapper userPaidAlbumMapper;
	@Autowired
	private UserPaidTrackMapper userPaidTrackMapper;
	@Autowired
	private MongoTemplate mongoTemplate;

	//微信一键登录
	@Override
	public Map<String, String> wxLogin(String code) {
		try {
			//拿着临时凭据+应用ID+应用密钥 调用微信接口 获取当前微信账户唯一标识 openid
			//微信账户信息业务类
			WxMaUserService userService = wxMaService.getUserService();
			//获取会话信息
			WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);
			//获取微信账号唯一标识
			String openid = sessionInfo.getOpenid();
			//根据账户唯一标识,查询数据库,看当前微信是否已经注册
			UserInfo userInfo = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
					.eq(UserInfo::getWxOpenId, openid));
			//如果微信账户首次注册,则新增用户记录,为用户初始化账户记录用于后续订单支付
			if(userInfo==null){
				//新增用户记录 绑定微信账户唯一标识
				 userInfo= new UserInfo();
				userInfo.setWxOpenId(openid);
				userInfo.setNickname(IdUtil.nanoId());
				userInfo.setAvatarUrl("http://192.168.200.6:9000/tingshu/2024-04-02/0b033705-4603-4fb2-bd0f-db84076aef84.jpg");
				userInfoMapper.insert(userInfo);
				//ToDo 为当前注册用户初始化账户记录 采用MQ可靠消息队列实现数据最终一致 (消费端)
				Map<String, Object> map = new HashMap<>();
                map.put("userId",userInfo.getId());
				map.put("title","新用户体验金活动");
				map.put("amount",new BigDecimal("100"));
				map.put("orderNo","ZS"+IdUtil.getSnowflakeNextId());
				//发送消息到MQ
				rabbitService.sendMessage(MqConst.EXCHANGE_USER,MqConst.ROUTING_USER_REGISTER,map);
			}
			//基于当前用户信息生成令牌
			//创建令牌
			String token = IdUtil.randomUUID();
			//构建登录成功后Redis的Key 形式为: user:login:token
			String tokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX+token;
			//构建登录成功后Redis的Value 形式为: userInfo
			UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
			//存入Redis 设置有效期: 7天
			redisTemplate.opsForValue().set(tokenKey,userInfoVo,RedisConstant.USER_LOGIN_REFRESH_KEY_TIMEOUT, TimeUnit.SECONDS);
			//封装令牌返回给前端
			Map<String, String> map = new HashMap<>();
			map.put("token",token);
			return map;
		} catch (WxErrorException e) {
			log.error("微信登录失败");
			throw new GuiguException(500,"微信登录失败");
		}
	}

	@Override
	public UserInfoVo getUserInfo(Long userId) {
		UserInfo userInfo = userInfoMapper.selectById(userId);
		if(userInfo !=null){
		  return 	BeanUtil.copyProperties(userInfo, UserInfoVo.class);
		}
		return null;
	}

	@Override
	public void updateUser(UserInfoVo userInfoVo) {
		Long userId = AuthContextHolder.getUserId();
		UserInfo userInfo = new UserInfo();
		userInfo.setId(userId);
		userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
		userInfo.setNickname(userInfoVo.getNickname());
		userInfoMapper.updateById(userInfo);
	}

	//提交需要检查购买状态的声音ID列表,响应每个声音的购买状态
	@Override
	public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStatusTrackIdList) {
		HashMap<Long, Integer> map = new HashMap<>();
		//根据用户ID+专辑ID查询专辑购买记录
		Long count = userPaidAlbumMapper.selectCount(
				new LambdaQueryWrapper<UserPaidAlbum>()
						.eq(UserPaidAlbum::getUserId, userId)
						.eq(UserPaidAlbum::getAlbumId, albumId)
		);
		//如果已购买专辑,将所有待检查购买状态声音 购买状态设置为1响应
    if(count>0){
		for (Long trackId : needCheckPayStatusTrackIdList) {
			map.put(trackId,1);
		}
		return map;
	}
		//根据用户ID加专辑ID查询已购声音记录
		List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
				new LambdaQueryWrapper<UserPaidTrack>()
						.eq(UserPaidTrack::getUserId, userId)
						.eq(UserPaidTrack::getAlbumId, albumId)
						.select(UserPaidTrack::getTrackId)
		);
		//如果不再已购声音,将所以待检查购买状态声音 购买状态都设置为0 响应
        if(ObjectUtil.isEmpty(userPaidTrackList)){
			for (Long trackId : needCheckPayStatusTrackIdList) {
				map.put(trackId,0);
			}
			return map;
		}
		//如果存在已购声音,将提交检查声音ID列表中,已购买状态设置为1 未购买设置为0
		   //用户已经买过的声音ID列表
		List<Long> userPaidTrackIdList = userPaidTrackList.stream()
				.map(UserPaidTrack::getTrackId)
				.collect(Collectors.toList());
		for (Long trackId : needCheckPayStatusTrackIdList) {
			if(userPaidTrackIdList.contains(trackId)){
				map.put(trackId,1);
			}else {
				map.put(trackId,0);
			}
		}
		return map;
	}



	//获取声音播放进度
	@Override
	public BigDecimal getTrackBreakSecond(Long userId, Long trackId) {
      //根据用户ID确定操作集合名称 形式为: 固定前缀_用户ID=userListenProcess_userId
         String  collectionName=this.getCollectionName(userId);
	  //查询条件: 用户ID+声音ID
		Query query = new Query();
		query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(trackId));
		//执行查询MongoDB
		UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
		if(listenProcess !=null){
			return listenProcess.getBreakSecond();
		}
		return BigDecimal.valueOf(0);
	}

	//获取用户播放进度集合名称
	@Override
	public String getCollectionName(Long userId) {
		String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId);
		return collectionName;
	}

	@Override
	public void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo) {
		//获取用户播放进度集合名称
		String collectionName = this.getCollectionName(userId);
		//根据用户ID+声音ID查询用户播放进度
		Query query = new Query(Criteria
				.where("userId").is(userId)
				.and("trackId").is(userListenProcessVo.getTrackId()));

		UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);

		//如果播放进度不存在,构建播放进度.保存到MongoDB
		//设置播放秒数小数位，舍入模式
		userListenProcessVo.setBreakSecond(userListenProcessVo.getBreakSecond().setScale(0, RoundingMode.DOWN));
         if(userListenProcess ==null){
			  userListenProcess = new UserListenProcess();
			  userListenProcess.setUserId(userId);
			 userListenProcess.setAlbumId(userListenProcessVo.getAlbumId());
			 userListenProcess.setTrackId(userListenProcessVo.getTrackId());
			 userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond());
			 userListenProcess.setCreateTime(new Date());
			 userListenProcess.setUpdateTime(new Date());
		 }else {
			 //如果播放进度存在,更新播放秒数
			 userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond());
			 userListenProcess.setUpdateTime(new Date());
		 }
             mongoTemplate.save(userListenProcess,collectionName);
		//ToDo 基于MQ可靠性消息,更新声音/专辑在MySQL以及ES中统计数值
		//当天内判断当前用户是否为第一次收听更新统计数值 核心: 生产者消息幂等性
		//关键点找出业务标识作为RedisKey: 前缀:用户ID_专辑ID_声音ID
		String redisKey=
         RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX+userId+"_"+userListenProcessVo.getAlbumId()+"_"+userListenProcessVo.getTrackId();
		//采用Redis提供set K V EX NX 命令 设置RedisKey ,设置过期时间,写入成功则发送MQ消息 更新统计数值:写入失败则忽略
		Date date = new Date();
		long ttl=DateUtil.endOfDay(date).getTime()- date.getTime();
		Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", ttl, TimeUnit.MILLISECONDS);
		//发送MQ消息,广播消息(通知"专辑服务"更新DB,通知"搜素服务"更新ES)
		if(flag){
			// 构建MQ消息VO对象 包含：专辑ID、声音ID、统计类型、增量数值、businessNo-UUID（用于消费者端幂等性处理），TODO VO必须序列化接口
			TrackStatMqVo mqVo = new TrackStatMqVo();
			mqVo.setBusinessNo(IdUtil.fastUUID());
			mqVo.setAlbumId(userListenProcessVo.getAlbumId());
			mqVo.setTrackId(userListenProcessVo.getTrackId());
			mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
			mqVo.setCount(1);
			//发送消息
			rabbitService.sendMessage(MqConst.EXCHANGE_TRACK,MqConst.ROUTING_TRACK_STAT_UPDATE,mqVo);
		}

	}
}
